/*===========================================================================*\
|
|  File:        cgremote.cpp
|
|  Description:
|   Routines to send and receive remote actions for controlling objects
|   on remote machines.  Uses DirectPlay for transferring data.
|       
|-----------------------------------------------------------------------------
|
|  Copyright (C) 1995-1996 Microsoft Corporation.  All Rights Reserved.
|
|  Written by Moss Bay Engineering, Inc. under contract to Microsoft Corporation
|
\*===========================================================================*/

/**************************************************************************

    (C) Copyright 1995-1996 Microsoft Corp.  All rights reserved.

    You have a royalty-free right to use, modify, reproduce and 
    distribute the Sample Files (and/or any modified version) in 
    any way you find useful, provided that you agree that 
    Microsoft has no warranty obligations or liability for any 
    Sample Application Files which are modified. 

    we do not recomend you base your game on IKlowns, start with one of
    the other simpler sample apps in the GDK

 **************************************************************************/

//** include files **
#include <stdlib.h>
#ifndef __WATCOMC__
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "cgglobl.h" // For ghInst for dialog box
#include "cgrsrce.h"
#include "dplay.h"
#include "dplobby.h"
#include "cglevel.h"
#include "cgremote.h"
#include "strrec.h"

#include <stdarg.h>
#include <stdio.h>

static void InitReceivePoll( void );

// [johnhall]
// hack -- get GENERIC_CHAR_INFO from cgkrusty.cpp
typedef signed char SCHAR;

typedef struct 
{
    LONG    posx;
    LONG    posy;
    SCHAR   state;
    SCHAR   velx;
    SCHAR   vely;
    SCHAR   curZ;
} GENERIC_CHAR_INFO;

//** local definitions **
// structure to be used to pass remote actions across the link
typedef struct _GAMEMESSAGE {
    BYTE                    Action;     // action code
    BYTE            NumBytes;   // size of data 
    REMOTE_OBJECT       RemObj;     // unique object id
    char            Data[1];    // action specific data
} GAMEMESSAGE, *PGAMEMESSAGE;


//** external functions **

//** external data **
// KLUDGE:
CGameLevel  *gCurLevel=NULL;

//** public data **
//** private data **
static  IDirectPlay *lpIDC=NULL;    // DirectPlay Object
static  DPID    dcoID=0;        // our DirectPlay ID
static  HANDLE  dphEvent = NULL;

static BOOL fAbort = FALSE; // Abort flag for RemoteConnect()

//** public functions **
//** private functions **
static DWORD RequestThreadProc(void *Dummy);
static BOOL CALLBACK AbortDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );


#define DP_BROADCAST_ID  0

// ----------------------------------------------------------
// CreateRemotePeer - create a new character across all peers
// ----------------------------------------------------------
REMOTE_OBJECT *CreateRemotePeers(
    char    *name,      // name of object as per level->Add() 
    DWORD   InstanceID  // unique instance ID of object type
)
{
    // Gotta have a DCO object to do this!
    if (lpIDC == NULL)
    {
        return(NULL);
    }

    // Setup a message and an unique obect id
    char *pBuffer = NULL;
    DWORD lenBuff;
    PGAMEMESSAGE    pGameMsg;
    REMOTE_OBJECT *pObj = new REMOTE_OBJECT;
    memset(pObj, 0, sizeof(REMOTE_OBJECT));

    // Make buffer big enough to hold game message plus
    // leading non-system-char byte
    lenBuff = sizeof(GAMEMESSAGE);
    pBuffer = new char[lenBuff];
    pGameMsg = (PGAMEMESSAGE)&pBuffer[0];

    // Fill in object id info so that this object is unique across
    // all machines
    pObj->OwnerID = (BYTE) dcoID;
    pObj->InstanceID = (BYTE) InstanceID;
    lstrcpyn(pObj->ObjectID, name, MAX_OBJ_NAME);
    memcpy(&pGameMsg->RemObj, pObj, sizeof(REMOTE_OBJECT));

    pGameMsg->Action = (BYTE) CREATE_OBJECT;

    // Broadcast it to everyone in the group.
    lpIDC->Send( dcoID,  // From
                 DP_BROADCAST_ID,
                 0,
                 (LPSTR)pBuffer,
                 lenBuff);  

    // Delete buffer, once sent
    delete []pBuffer;

    return(pObj);
}   





// ----------------------------------------------------------
// SendRemoteAction - broadcast an action to remote peers
// ----------------------------------------------------------
BOOL SendRemoteAction(
    REMOTE_OBJECT   *pObj,      // unique object id
    ACTION      Action,     // action code 
    void        *Data,      // action data
    DWORD       nDataSize   // size of action data
)
{
    PGAMEMESSAGE    gameMsg;
    char *pBuffer;
    DWORD lenBuff;
      //   char chBuffer[128];



    // Gotta have a valid DirectPlay object
    if (lpIDC == NULL)
    {
        return(NULL);
    }

    // Allocate the GAME buffer with room for data,
    // plus room at the beginning for a non-sytem-message char
    lenBuff = sizeof(GAMEMESSAGE) + nDataSize; 
    pBuffer = new char [lenBuff]; 
    // Now point gameMsg at the rest of the buffer
    gameMsg = (PGAMEMESSAGE)&pBuffer[0];

    // Copy the action code and data to message buffer
    gameMsg->Action = Action;
    memcpy(&gameMsg->RemObj, pObj, sizeof(REMOTE_OBJECT));
    gameMsg->NumBytes = (BYTE) nDataSize;
    memcpy(gameMsg->Data, Data, nDataSize);
#if 0
        wsprintf( chBuffer, "%d (10%s) %d %d %d",
            gameMsg->Action,
            gameMsg->RemObj.ObjectID,
            gameMsg->RemObj.InstanceID,
            gameMsg->RemObj.OwnerID,
            gameMsg->NumBytes);
        OutputDebugString(chBuffer);
        if (nDataSize == sizeof(GENERIC_CHAR_INFO))
        {
            GENERIC_CHAR_INFO *pci;
            pci = (GENERIC_CHAR_INFO *) Data;
            wsprintf( chBuffer,"; %d %d %d %d %d %d",
                pci->state,
                pci->posx,
                pci->posy,
                pci->velx,
                pci->vely,
                pci->curZ);
            OutputDebugString(chBuffer);
        }
        OutputDebugString("\r\n");
#endif

    // Broadcast the action to all peers
    lpIDC->Send(dcoID, // from
                DP_BROADCAST_ID, // to
                0,
                pBuffer,
                lenBuff);   
    delete []pBuffer;
    return(TRUE);
}   

// ----------------------------------------------------------
// DestroyRemotePeer - Tell remote peers to kill an object
// ----------------------------------------------------------
BOOL DestroyRemotePeer(
    REMOTE_OBJECT *pObj     // unique object id
)
{
    PGAMEMESSAGE    gameMsg;
    char *pBuffer = NULL;
    DWORD lenBuff;

    if (pObj == NULL)
        return(TRUE);

    // Allocate a buffer for the GAME buffer with roon for data,
    // plus an extra character at the beginning to be the non-system
    // message char.
    lenBuff = sizeof(GAMEMESSAGE)+1; 
    pBuffer = new char [lenBuff];

    // Point the game message pointer at the rest of the buffer
    gameMsg = (PGAMEMESSAGE)&pBuffer[0];

    // Format a destroy message
    memcpy(&gameMsg->RemObj, pObj, sizeof(REMOTE_OBJECT));
    gameMsg->RemObj.OwnerID = (BYTE) dcoID;
    gameMsg->NumBytes = 0;
    gameMsg->Action = (BYTE) DESTROY_OBJECT;

    // Broadcast the destroy message to all peers
    lpIDC->Send( dcoID, 
                 DP_BROADCAST_ID,
                 0,
                 pBuffer,
                 lenBuff);  
    // Delete buffer
    delete []pBuffer;

    // Don't need the object id anymore
    delete pObj;
    return(TRUE);
}   

// -----------------------------------------------------------------
// RemoteConnect - establish an active connection with remotes
// -----------------------------------------------------------------

BOOL FAR PASCAL EnumSession(
        LPDPSESSIONDESC lpDPGameDesc,
        LPVOID pContext,
        LPDWORD pTimeOut,
        DWORD dwFlags )
{
    LONG iIndex;
    HWND hWnd = (HWND) pContext;

    if( dwFlags == DPESC_TIMEDOUT )
    {
    return FALSE;
    }

    iIndex = SendMessage(hWnd, LB_ADDSTRING, 0, (LPARAM) lpDPGameDesc->szSessionName);
    if (iIndex != LB_ERR)
        SendMessage(hWnd, LB_SETITEMDATA, iIndex, (LPARAM) lpDPGameDesc->dwSession);

    SetFocus(hWnd);
    SendMessage(hWnd, LB_SETCURSEL, 0, 0);
    return(TRUE);

}

BOOL FAR PASCAL EnumSP(LPGUID lpGuid, LPSTR lpDesc,
            DWORD dwMajor, DWORD dwMinor, LPVOID lpv)
{
    LONG iIndex;
    HWND hWnd = (HWND) lpv;

    iIndex = SendMessage(hWnd, LB_ADDSTRING, 0, (LPARAM) lpDesc);
    if (iIndex != LB_ERR)
        SendMessage(hWnd, LB_SETITEMDATA, iIndex, (LPARAM) lpGuid);

    SetFocus(hWnd);
    SendMessage(hWnd, LB_SETCURSEL, 0, 0);
    return(TRUE);
}



BOOL CALLBACK DlgProcQCreate (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_COMMAND:
        switch(wParam)
        {
        case IDC_CREATE:
            EndDialog(hDlg, 1);
            return(TRUE);

        case IDC_CONNECT:
            EndDialog(hDlg, 2);
            return(TRUE);

        case IDCANCEL:
            EndDialog(hDlg, -1);
            return(TRUE);
        }
        break;

    }
    return(FALSE);
}
BOOL CALLBACK DlgProcChooseProvider (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    LPGUID  lpGuid;
    static  LONG    iIndex;
    static  HWND hWndCtl;

    switch (msg)
    {
    case WM_INITDIALOG:

        hWndCtl = GetDlgItem(hDlg, IDC_LIST1);
        if (hWndCtl == NULL)
        {
            EndDialog(hDlg, TRUE);
            return(TRUE);
        }
        DirectPlayEnumerate(EnumSP, (LPVOID) hWndCtl);
        SetFocus(hWndCtl);
        SendMessage(hWndCtl, LB_SETCURSEL, 0, 0);
        return(FALSE);

    case WM_COMMAND:

        switch( HIWORD(wParam))
        {
        case LBN_SELCHANGE:
            iIndex = SendMessage((HWND) lParam, LB_GETCURSEL, 0, 0);
            hWndCtl = (HWND) lParam;
            return(FALSE);

        case LBN_DBLCLK:
            iIndex = SendMessage((HWND) lParam, LB_GETCURSEL, 0, 0);
            if (iIndex != LB_ERR)
            {
                lpGuid = (LPGUID) SendMessage((HWND) lParam, LB_GETITEMDATA, iIndex, 0);
                DirectPlayCreate(lpGuid, &lpIDC, NULL);
                EndDialog(hDlg, TRUE);
                return(TRUE);
            }
            break;

        case 0:
            if (LOWORD(wParam) == IDOK)
            {
                if (iIndex != LB_ERR)
                {
                    lpGuid = (LPGUID) SendMessage(hWndCtl, LB_GETITEMDATA, iIndex, 0);
                    if (lpGuid)
                    {
                        DirectPlayCreate(lpGuid, &lpIDC, NULL);
                        EndDialog(hDlg, TRUE);
                    }
                    else
                        EndDialog(hDlg, FALSE);
                    return(TRUE);
                }
            }
            else if (LOWORD(wParam) == IDCANCEL)
            {
                EndDialog(hDlg, FALSE);
                return(TRUE);
            }
            break;

        }
    }
    return (FALSE);
}

LPGUID g_lpGuid;

BOOL CALLBACK DlgProcSelSession (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static  LONG    iIndex;
    static  HWND hWndCtl;
    DPSESSIONDESC dpDesc;
    HRESULT hr = DP_OK + 10;

    switch (msg)
    {
    case WM_INITDIALOG:

        hWndCtl = GetDlgItem(hDlg, IDC_LB_SESSION);
        if (hWndCtl == NULL)
        {
            EndDialog(hDlg, TRUE);
            return(TRUE);
        }
        memset(&dpDesc, 0x00, sizeof(DPSESSIONDESC));
        dpDesc.dwSize = sizeof(dpDesc);
        dpDesc.guidSession = *g_lpGuid;
        lpIDC->EnumSessions(&dpDesc, 5000, EnumSession, (LPVOID) hWndCtl, 0 );

        SetFocus(hWndCtl);
        return(FALSE);

    case WM_COMMAND:

        switch( HIWORD(wParam))
        {
        case LBN_SELCHANGE:
            iIndex = SendMessage((HWND) lParam, LB_GETCURSEL, 0, 0);
            hWndCtl = (HWND) lParam;
            return(FALSE);

        case 0:
            if (LOWORD(wParam) == IDCANCEL)
            {
                lpIDC->Close();
                lpIDC->Release();
                lpIDC = NULL;
                EndDialog(hDlg, FALSE);
                return(TRUE);
            }
            //
            // Fall Through.
            //
        case LBN_DBLCLK:
            if (HIWORD(wParam) == LBN_DBLCLK)
            {
                hWndCtl = (HWND) lParam;
                iIndex = SendMessage(hWndCtl, LB_GETCURSEL, 0, 0);
            }

            if (iIndex != LB_ERR)
            {
                memset(&dpDesc, 0x00, sizeof(DPSESSIONDESC));
                dpDesc.dwSize       = sizeof(dpDesc);
                dpDesc.guidSession  = *g_lpGuid;
                dpDesc.dwFlags      = DPOPEN_OPENSESSION;
                dpDesc.dwSession    = SendMessage((HWND) hWndCtl, LB_GETITEMDATA, iIndex, 0);
                hr = lpIDC->Open(&dpDesc);

                if (hr != DP_OK)
                {
                    lpIDC->Close();
                    lpIDC->Release();
                    lpIDC = NULL;
                    EndDialog(hDlg, FALSE);
                }

                EndDialog(hDlg, TRUE);
                return(TRUE);

            }
        }
    }
    return (FALSE);
}

INT GetProvider()
{

  return(DialogBox (NULL, (LPCTSTR) IDD_CHOOSEPROVIDER, NULL, (DLGPROC) DlgProcChooseProvider));
}

INT CreateGame()
{
  return(DialogBox (NULL, (LPCTSTR) IDD_Q_CREATE, NULL, (DLGPROC) DlgProcQCreate));
    
}

INT GetGame()
{

  return(DialogBox (NULL, (LPCTSTR) IDD_SELSESSION, NULL, (DLGPROC) DlgProcSelSession));
}

BOOL RemoteCreateLobby(void)
{
    LPDIRECTPLAY2A      lpDPlay2 = NULL;
    LPDIRECTPLAYLOBBYA  lpDPLobby = NULL;
    HRESULT             hr;

    // Be sure we aren't already initialized.
    if (lpIDC != NULL)
    {
        return( FALSE );
    }

    // create a lobby object
    hr = DirectPlayLobbyCreate(NULL, &lpDPLobby, NULL, NULL, 0);
    if FAILED(hr)
        return (FALSE);

    // try to connect using the lobby
    hr = lpDPLobby->Connect(0, &lpDPlay2, NULL) ;   

    // lobby launched us, so get a DirectPlay 1 interface
    if SUCCEEDED(hr)
    {
        // query for a DirectPlay 1.0 interface
        hr = lpDPlay2->QueryInterface(IID_IDirectPlay, (LPVOID *) &lpIDC);

        // release the ANSI DirectPlay2 interface
        lpDPlay2->Release();
    }

    // release lobby interface
    lpDPLobby->Release();

    return ((BOOL)(hr == DP_OK));
}

BOOL RemoteCreate(REFGUID pGuid, LPSTR FullName, LPSTR NickName)
{
    HRESULT hr;
    DPSESSIONDESC dpDesc;

    // lobby did not launch us, so ask user for connection settings
    if (lpIDC == NULL)
    {
        GetProvider();

        if (lpIDC == NULL)
            return(FALSE);

        switch( CreateGame())
        {
        case 1:             // Create
            memset(&dpDesc, 0x00, sizeof(DPSESSIONDESC));
            dpDesc.dwSize = sizeof(dpDesc);
            dpDesc.dwMaxPlayers = 10;
            dpDesc.dwFlags = DPOPEN_CREATESESSION;
            dpDesc.guidSession = pGuid;
            strcpy( dpDesc.szSessionName, FullName);
        
            if ((hr = lpIDC->Open(&dpDesc)) != DP_OK)
            {
                lpIDC->Release();
                lpIDC = NULL;
                return(FALSE);
            }
        
            break;

        case 2:             // Connect
            g_lpGuid = (LPGUID) &pGuid;

            GetGame();

            if (lpIDC == NULL)
                return(FALSE);

            break;

        default:
            return(FALSE);
        }
    }

    if ((hr = lpIDC->CreatePlayer(&dcoID, NickName,
                                  "IKlowns Player", &dphEvent)) != DP_OK)
    {
        lpIDC->Close();
        lpIDC->Release();
        lpIDC = NULL;
        return(FALSE);
    }
    
    
    InitReceivePoll();
    return(TRUE);

}


// -----------------------------------------------------------------
// SetCurrentLevel - set pointer to current level object
// -----------------------------------------------------------------
void SetCurrentLevel(
    void    *newLevel
)
{
    gCurLevel = (CGameLevel *)newLevel;
}

// ----------------------------------------------------------
// CreateNewCharacter - create a new game object!
// ----------------------------------------------------------
void CreateNewCharacter(
    REMOTE_OBJECT *pObj // unique object id
)
{
    char dataBuf[256];
    char graphicsBuf[256];

    // Get specific data on object from profile
    // NOTE: This means we hit the disk.  For performance,
    // it would be better if objects could be created based
    // on an in memory object so that we don't ever have to
    // hit the disk!


    GetPrivateProfileString(
            gCurLevel->GetLevelName(),
            "Graphics",
            "",
            graphicsBuf,
            sizeof( graphicsBuf ),
            gCurLevel->GetProfileName()
            );

    GetPrivateProfileString(
            graphicsBuf,
            pObj->ObjectID,
            "",
            dataBuf,
            sizeof( dataBuf ),
            gCurLevel->GetProfileName()
            );

    // parse the data string into fields
    CStringRecord fields( dataBuf, "," );

    if (fields.GetNumFields() >= 5)
    {
        // Add the object to the game list!
        gCurLevel->Add(pObj->ObjectID,
            atoi(fields[1]), 
            atoi(fields[3]),
            atoi(fields[4]),
            (void *)pObj);
    }
}

// ----------------------------------------------------------
// ProcessIncomingActions - Parse received messages & queue them
// ----------------------------------------------------------
BOOL ProcessIncomingActions(
    REMOTE_OBJECT *pObj,    // unique object id
    ACTION action,      // action code
    void *Data,     // action data
    DWORD nDataSize     // sizeof action data
)
{
    CLinkedList *ActionList;
    BOOL        fQueuedUp = FALSE;
        // char            chBuffer[128];

        // wsprintf( chBuffer, "IncomingAction %d size %d\r\n", action, nDataSize);
        // OutputDebugString(chBuffer);

    // Guard against activity occurring before we are ready!
    if (gCurLevel == NULL)
        return(FALSE);

    // Don't do anything about our own requests
    if (pObj->OwnerID == dcoID)
        return(FALSE);
        
    // Figure out what to do with this message
    switch (action) {

    // Need to create a brand new object locally
        case CREATE_OBJECT: {

                // OutputDebugString("CreateObject \r\n");
        // Create a game character
        if (!FindRemoteObjectEntry(pObj))
        {
                        // OutputDebugString("CreateNewCharacter \r\n");
            CreateNewCharacter(pObj);
        }
                // OutputDebugString("Leave CreateObject \r\n");
        break;
    }

    // Time to destroy the object!
    case DESTROY_OBJECT:    {
        REMOTE_DATA *pRemoteEntry = FindRemoteObjectEntry(pObj);

                // OutputDebugString("DestroyObject \r\n");
        // Need to tell level object to remove the character!
        if (pRemoteEntry != NULL)
            gCurLevel->Remove((CGameGraphic *)pRemoteEntry->Data);

        break;
    }

    // Must be some character specific action
    default:{

        // Get action list for this object
        ActionList = GetRemoteObjectQueue(pObj);

        // No action list -> a new object, so try to create it.
        if (ActionList == NULL)
        {
            CreateNewCharacter(pObj);
            ActionList = GetRemoteObjectQueue(pObj);
            if (ActionList == NULL)
                return(FALSE);
        }

        // Place this action onto the queue
                // OutputDebugString("QueueRemoteAction \r\n");
        QueueRemoteAction(ActionList, action, Data, nDataSize);
        fQueuedUp = TRUE;
        break;
    }
    }
    return(fQueuedUp);
}

// ----------------------------------------------------------
// ReleaseRemoteData - free remote buffer
// ----------------------------------------------------------
void ReleaseRemoteData(
    LPVOID  pData       // ptr to app data portion
)
{
    PGAMEMESSAGE    pMsg;   // desired pointer
    int     iDiff;  // difference between the two

    // Pretend we got passed in the proper ptr
    pMsg = (PGAMEMESSAGE)pData;

    // Calculate the difference between what we got
    // and what we need.
    iDiff = (LPBYTE)(&pMsg->Data) - (LPBYTE)(pMsg);

    // Adjust accordingly and free the message!
    pMsg = (PGAMEMESSAGE)((LPBYTE)pMsg - iDiff);
    delete pMsg;
}

#define MAX_BUFFER_SIZE (sizeof(GAMEMESSAGE)+512)




static char *pBuffer = NULL;
static DWORD lenBuff = MAX_BUFFER_SIZE; 

static void InitReceivePoll( void )
{
    // We allocate a buffer to receive into first,
    // which may have to cope with system messages,
    // before copying the real message to the buffer to pass
    // to the game
    pBuffer = new char[ lenBuff ];

}

void PollForRemoteReceive( void )
{
    DPID        fromID, dcoReceiveID;
    DWORD       nBytes;
    PGAMEMESSAGE    pMsg;
    int         i;
    const       int MAX_MESSAGES = 16;
    BOOL        fCheckForMore = TRUE;

    // Paranoia check
    if ( lpIDC )
    {
        // We try to receive MAX_MESSAGES at a time so that
        // a backlog doesn't build up.  If we run out of messages,
        // we stop looking for them.

        for( i = 0; i < MAX_MESSAGES && fCheckForMore ; i++ )
        {
            HRESULT status;
            nBytes = lenBuff;
            status = lpIDC->Receive(
                        &fromID,
                        &dcoReceiveID,
                        DPRECEIVE_ALL,
                        pBuffer,
                        &nBytes);
            switch( status )
            {
            case DP_OK:
                if ( fromID == 0 )
                {
                    // We do not in fact utilise the system
                    // messages during the game.
                }
                else
                {
                    // User message - we copy the buffer minus the
                    // user message byte to the game buffer, then 
                    // pass it on.
        
                    // Allocate buffer for game message
                    pMsg = (PGAMEMESSAGE)new char [MAX_BUFFER_SIZE];
                    memcpy( pMsg, &pBuffer[0], nBytes );
        
                if (!ProcessIncomingActions(&pMsg->RemObj, pMsg->Action,
                                pMsg->Data, nBytes))
                        delete pMsg;
                }
            break;

            default:
                // Error condition of some kind - we just stop
                // checking for now
                fCheckForMore = FALSE;
                break;
            }
        }
    }
}


BOOL CALLBACK AbortDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    WORD wID, wNotifyCode;
    BOOL retVal = 0;

    switch( uMsg )
    {
        case WM_INITDIALOG:
            // We didn't set focus
            retVal = 1;
            break;
        case WM_COMMAND:
            wID = LOWORD( wParam );
            wNotifyCode = HIWORD( wParam );
            switch ( wID )
            {
                case IDCANCEL:
                    if ( BN_CLICKED == wNotifyCode ) {
                        fAbort = TRUE;
                        retVal = 1;
                    }
                    break;
                default:
                    break;
            }   
            break;
    }
    return( retVal );
}
